home *** CD-ROM | disk | FTP | other *** search
/ Amiga Plus 2004 #11 / Amiga Plus CD - 2004 - No. 11.iso / AmiSoft / Comm / www / tidy_os4.lha / tidy / src / clean.c < prev    next >
C/C++ Source or Header  |  2004-07-25  |  64KB  |  2,571 lines

  1. /*
  2.   clean.c -- clean up misuse of presentation markup
  3.  
  4.   (c) 1998-2004 (W3C) MIT, ERCIM, Keio University
  5.   See tidy.h for the copyright notice.
  6.  
  7.   CVS Info :
  8.  
  9.     $Author: hoehrmann $ 
  10.     $Date: 2004/06/18 21:10:30 $ 
  11.     $Revision: 1.63 $ 
  12.  
  13.   Filters from other formats such as Microsoft Word
  14.   often make excessive use of presentation markup such
  15.   as font tags, B, I, and the align attribute. By applying
  16.   a set of production rules, it is straight forward to
  17.   transform this to use CSS.
  18.  
  19.   Some rules replace some of the children of an element by
  20.   style properties on the element, e.g.
  21.  
  22.   <p><b>...</b></p> -> <p style="font-weight: bold">...</p>
  23.  
  24.   Such rules are applied to the element's content and then
  25.   to the element itself until none of the rules more apply.
  26.   Having applied all the rules to an element, it will have
  27.   a style attribute with one or more properties. 
  28.  
  29.   Other rules strip the element they apply to, replacing
  30.   it by style properties on the contents, e.g.
  31.   
  32.   <dir><li><p>...</li></dir> -> <p style="margin-left 1em">...
  33.       
  34.   These rules are applied to an element before processing
  35.   its content and replace the current element by the first
  36.   element in the exposed content.
  37.  
  38.   After applying both sets of rules, you can replace the
  39.   style attribute by a class value and style rule in the
  40.   document head. To support this, an association of styles
  41.   and class names is built.
  42.  
  43.   A naive approach is to rely on string matching to test
  44.   when two property lists are the same. A better approach
  45.   would be to first sort the properties before matching.
  46.  
  47. */
  48.  
  49. #include <stdio.h>
  50. #include <stdlib.h>
  51. #include <string.h>
  52.  
  53. #include "tidy-int.h"
  54. #include "clean.h"
  55. #include "lexer.h"
  56. #include "parser.h"
  57. #include "attrs.h"
  58. #include "message.h"
  59. #include "tmbstr.h"
  60. #include "utf8.h"
  61.  
  62. void RenameElem( Node* node, TidyTagId tid )
  63. {
  64.     const Dict* dict = LookupTagDef( tid );
  65.     MemFree( node->element );
  66.     node->element = tmbstrdup( dict->name );
  67.     node->tag = dict;
  68. }
  69.  
  70. static void FreeStyleProps(StyleProp *props)
  71. {
  72.     StyleProp *next;
  73.  
  74.     while (props)
  75.     {
  76.         next = props->next;
  77.         MemFree(props->name);
  78.         MemFree(props->value);
  79.         MemFree(props);
  80.         props = next;
  81.     }
  82. }
  83.  
  84. static StyleProp *InsertProperty( StyleProp* props, ctmbstr name, ctmbstr value )
  85. {
  86.     StyleProp *first, *prev, *prop;
  87.     int cmp;
  88.  
  89.     prev = NULL;
  90.     first = props;
  91.  
  92.     while (props)
  93.     {
  94.         cmp = tmbstrcmp(props->name, name);
  95.  
  96.         if (cmp == 0)
  97.         {
  98.             /* this property is already defined, ignore new value */
  99.             return first;
  100.         }
  101.  
  102.         if (cmp > 0)
  103.         {
  104.             /* insert before this */
  105.  
  106.             prop = (StyleProp *)MemAlloc(sizeof(StyleProp));
  107.             prop->name = tmbstrdup(name);
  108.             prop->value = tmbstrdup(value);
  109.             prop->next = props;
  110.  
  111.             if (prev)
  112.                 prev->next = prop;
  113.             else
  114.                 first = prop;
  115.  
  116.             return first;
  117.         }
  118.  
  119.         prev = props;
  120.         props = props->next;
  121.     }
  122.  
  123.     prop = (StyleProp *)MemAlloc(sizeof(StyleProp));
  124.     prop->name = tmbstrdup(name);
  125.     prop->value = tmbstrdup(value);
  126.     prop->next = NULL;
  127.  
  128.     if (prev)
  129.         prev->next = prop;
  130.     else
  131.         first = prop;
  132.  
  133.     return first;
  134. }
  135.  
  136. /*
  137.  Create sorted linked list of properties from style string
  138.  It temporarily places nulls in place of ':' and ';' to
  139.  delimit the strings for the property name and value.
  140.  Some systems don't allow you to NULL literal strings,
  141.  so to avoid this, a copy is made first.
  142. */
  143. static StyleProp* CreateProps( StyleProp* prop, ctmbstr style )
  144. {
  145.     tmbstr name, value = NULL, name_end, value_end, line;
  146.     Bool more;
  147.  
  148.     line = tmbstrdup(style);
  149.     name = line;
  150.  
  151.     while (*name)
  152.     {
  153.         while (*name == ' ')
  154.             ++name;
  155.  
  156.         name_end = name;
  157.  
  158.         while (*name_end)
  159.         {
  160.             if (*name_end == ':')
  161.             {
  162.                 value = name_end + 1;
  163.                 break;
  164.             }
  165.  
  166.             ++name_end;
  167.         }
  168.  
  169.         if (*name_end != ':')
  170.             break;
  171.  
  172.         while ( value && *value == ' ')
  173.             ++value;
  174.  
  175.         value_end = value;
  176.         more = no;
  177.  
  178.         while (*value_end)
  179.         {
  180.             if (*value_end == ';')
  181.             {
  182.                 more = yes;
  183.                 break;
  184.             }
  185.  
  186.             ++value_end;
  187.         }
  188.  
  189.         *name_end = '\0';
  190.         *value_end = '\0';
  191.  
  192.         prop = InsertProperty(prop, name, value);
  193.         *name_end = ':';
  194.  
  195.         if (more)
  196.         {
  197.             *value_end = ';';
  198.             name = value_end + 1;
  199.             continue;
  200.         }
  201.  
  202.         break;
  203.     }
  204.  
  205.     MemFree(line);  /* free temporary copy */
  206.     return prop;
  207. }
  208.  
  209. static tmbstr CreatePropString(StyleProp *props)
  210. {
  211.     tmbstr style, p, s;
  212.     int len;
  213.     StyleProp *prop;
  214.  
  215.     /* compute length */
  216.  
  217.     for (len = 0, prop = props; prop; prop = prop->next)
  218.     {
  219.         len += tmbstrlen(prop->name) + 2;
  220.         if (prop->value)
  221.             len += tmbstrlen(prop->value) + 2;
  222.     }
  223.  
  224.     style = (tmbstr) MemAlloc(len+1);
  225.  
  226.     for (p = style, prop = props; prop; prop = prop->next)
  227.     {
  228.         s = prop->name;
  229.  
  230.         while((*p++ = *s++))
  231.             continue;
  232.  
  233.         if (prop->value)
  234.         {
  235.             *--p = ':';
  236.             *++p = ' ';
  237.             ++p;
  238.  
  239.             s = prop->value;
  240.             while((*p++ = *s++))
  241.                 continue;
  242.         }
  243.         if (prop->next == NULL)
  244.             break;
  245.  
  246.         *--p = ';';
  247.         *++p = ' ';
  248.         ++p;
  249.     }
  250.  
  251.     return style;
  252. }
  253.  
  254. /*
  255.   create string with merged properties
  256. static tmbstr AddProperty( ctmbstr style, ctmbstr property )
  257. {
  258.     tmbstr line;
  259.     StyleProp *prop;
  260.  
  261.     prop = CreateProps(NULL, style);
  262.     prop = CreateProps(prop, property);
  263.     line = CreatePropString(prop);
  264.     FreeStyleProps(prop);
  265.     return line;
  266. }
  267. */
  268.  
  269. void FreeStyles( TidyDocImpl* doc )
  270. {
  271.     Lexer* lexer = doc->lexer;
  272.     if ( lexer )
  273.     {
  274.         TagStyle *style, *next;
  275.         for ( style = lexer->styles; style; style = next )
  276.         {
  277.             next = style->next;
  278.             MemFree( style->tag );
  279.             MemFree( style->tag_class );
  280.             MemFree( style->properties );
  281.             MemFree( style );
  282.         }
  283.     }
  284. }
  285.  
  286. static tmbstr GensymClass( TidyDocImpl* doc )
  287. {
  288.     tmbchar buf[512];  /* CSSPrefix is limited to 256 characters */
  289.     ctmbstr pfx = cfgStr(doc, TidyCSSPrefix);
  290.     if ( pfx == NULL || *pfx == 0 )
  291.       pfx = "c";
  292.  
  293.     tmbsnprintf(buf, sizeof(buf), "%s%d", pfx, ++doc->nClassId );
  294.     return tmbstrdup(buf);
  295. }
  296.  
  297. static ctmbstr FindStyle( TidyDocImpl* doc, ctmbstr tag, ctmbstr properties )
  298. {
  299.     Lexer* lexer = doc->lexer;
  300.     TagStyle* style;
  301.  
  302.     for (style = lexer->styles; style; style=style->next)
  303.     {
  304.         if (tmbstrcmp(style->tag, tag) == 0 &&
  305.             tmbstrcmp(style->properties, properties) == 0)
  306.             return style->tag_class;
  307.     }
  308.  
  309.     style = (TagStyle *)MemAlloc( sizeof(TagStyle) );
  310.     style->tag = tmbstrdup(tag);
  311.     style->tag_class = GensymClass( doc );
  312.     style->properties = tmbstrdup( properties );
  313.     style->next = lexer->styles;
  314.     lexer->styles = style;
  315.     return style->tag_class;
  316. }
  317.  
  318. /*
  319.  Add class="foo" to node
  320. */
  321. void AddClass( TidyDocImpl* doc, Node* node, ctmbstr classname )
  322. {
  323.     AttVal *classattr = AttrGetById(node, TidyAttr_CLASS);;
  324.  
  325.     /*
  326.      if there already is a class attribute
  327.      then append class name after a space.
  328.     */
  329.     if (classattr)
  330.     {
  331.         int len = tmbstrlen(classattr->value) +
  332.                   tmbstrlen(classname) + 2;
  333.         tmbstr s = (tmbstr) MemAlloc( len );
  334.         tmbstrcpy( s, classattr->value );
  335.         tmbstrcat( s, " " );
  336.         tmbstrcat( s, classname );
  337.         MemFree( classattr->value );
  338.         classattr->value = s;
  339.     }
  340.     else /* create new class attribute */
  341.         AddAttribute( doc, node, "class", classname );
  342. }
  343.  
  344.  
  345. /*
  346.  Find style attribute in node, and replace it
  347.  by corresponding class attribute. Search for
  348.  class in style dictionary otherwise gensym
  349.  new class and add to dictionary.
  350.  
  351.  Assumes that node doesn't have a class attribute
  352. */
  353. static void Style2Rule( TidyDocImpl* doc, Node *node)
  354. {
  355.     AttVal *styleattr, *classattr;
  356.     ctmbstr classname;
  357.  
  358.     styleattr = AttrGetById(node, TidyAttr_STYLE);
  359.  
  360.     if (styleattr)
  361.     {
  362.         /* fix for http://tidy.sf.net/bug/850215 */
  363.         if (!styleattr->value)
  364.         {
  365.             RemoveAttribute(doc, node, styleattr);
  366.             return;
  367.         }
  368.  
  369.         classname = FindStyle( doc, node->element, styleattr->value );
  370.         classattr = AttrGetById(node, TidyAttr_CLASS);
  371.  
  372.         /*
  373.          if there already is a class attribute
  374.          then append class name after an underscore
  375.         */
  376.         if (classattr)
  377.         {
  378.             int len = tmbstrlen(classattr->value) +
  379.                       tmbstrlen(classname) + 2;
  380.             tmbstr s = (tmbstr) MemAlloc( len );
  381.             if (classattr->value)
  382.             {
  383.                 tmbstrcpy(s, classattr->value);
  384.                 tmbstrcat(s, " ");
  385.             }
  386.             tmbstrcat(s, classname);
  387.             if (classattr->value)
  388.                 MemFree(classattr->value);
  389.             classattr->value = s;
  390.             RemoveAttribute( doc, node, styleattr );
  391.         }
  392.         else /* reuse style attribute for class attribute */
  393.         {
  394.             MemFree(styleattr->attribute);
  395.             MemFree(styleattr->value);
  396.             styleattr->attribute = tmbstrdup("class");
  397.             styleattr->value = tmbstrdup(classname);
  398.         }
  399.     }
  400. }
  401.  
  402. static void AddColorRule( Lexer* lexer, ctmbstr selector, ctmbstr color )
  403. {
  404.     if ( selector && color )
  405.     {
  406.         AddStringLiteral(lexer, selector);
  407.         AddStringLiteral(lexer, " { color: ");
  408.         AddStringLiteral(lexer, color);
  409.         AddStringLiteral(lexer, " }\n");
  410.     }
  411. }
  412.  
  413. /*
  414.  move presentation attribs from body to style element
  415.  
  416.  background="foo" ->  body { background-image: url(foo) }
  417.  bgcolor="foo"    ->  body { background-color: foo }
  418.  text="foo"       ->  body { color: foo }
  419.  link="foo"       ->  :link { color: foo }
  420.  vlink="foo"      ->  :visited { color: foo }
  421.  alink="foo"      ->  :active { color: foo }
  422. */
  423. static void CleanBodyAttrs( TidyDocImpl* doc, Node* body )
  424. {
  425.     Lexer* lexer  = doc->lexer;
  426.     tmbstr bgurl   = NULL;
  427.     tmbstr bgcolor = NULL;
  428.     tmbstr color   = NULL;
  429.     AttVal* attr;
  430.     
  431.     if (NULL != (attr = AttrGetById(body, TidyAttr_BACKGROUND)))
  432.     {
  433.         bgurl = attr->value;
  434.         attr->value = NULL;
  435.         RemoveAttribute( doc, body, attr );
  436.     }
  437.  
  438.     if (NULL != (attr = AttrGetById(body, TidyAttr_BGCOLOR)))
  439.     {
  440.         bgcolor = attr->value;
  441.         attr->value = NULL;
  442.         RemoveAttribute( doc, body, attr );
  443.     }
  444.  
  445.     if (NULL != (attr = AttrGetById(body, TidyAttr_TEXT)))
  446.     {
  447.         color = attr->value;
  448.         attr->value = NULL;
  449.         RemoveAttribute( doc, body, attr );
  450.     }
  451.  
  452.     if ( bgurl || bgcolor || color )
  453.     {
  454.         AddStringLiteral(lexer, " body {\n");
  455.         if (bgurl)
  456.         {
  457.             AddStringLiteral(lexer, "  background-image: url(");
  458.             AddStringLiteral(lexer, bgurl);
  459.             AddStringLiteral(lexer, ");\n");
  460.             MemFree(bgurl);
  461.         }
  462.         if (bgcolor)
  463.         {
  464.             AddStringLiteral(lexer, "  background-color: ");
  465.             AddStringLiteral(lexer, bgcolor);
  466.             AddStringLiteral(lexer, ";\n");
  467.             MemFree(bgcolor);
  468.         }
  469.         if (color)
  470.         {
  471.             AddStringLiteral(lexer, "  color: ");
  472.             AddStringLiteral(lexer, color);
  473.             AddStringLiteral(lexer, ";\n");
  474.             MemFree(color);
  475.         }
  476.  
  477.         AddStringLiteral(lexer, " }\n");
  478.     }
  479.  
  480.     if (NULL != (attr = AttrGetById(body, TidyAttr_LINK)))
  481.     {
  482.         AddColorRule(lexer, " :link", attr->value);
  483.         RemoveAttribute( doc, body, attr );
  484.     }
  485.  
  486.     if (NULL != (attr = AttrGetById(body, TidyAttr_VLINK)))
  487.     {
  488.         AddColorRule(lexer, " :visited", attr->value);
  489.         RemoveAttribute( doc, body, attr );
  490.     }
  491.  
  492.     if (NULL != (attr = AttrGetById(body, TidyAttr_ALINK)))
  493.     {
  494.         AddColorRule(lexer, " :active", attr->value);
  495.         RemoveAttribute( doc, body, attr );
  496.     }
  497. }
  498.  
  499. static Bool NiceBody( TidyDocImpl* doc )
  500. {
  501.     Node* node = FindBody(doc);
  502.     if (node)
  503.     {
  504.         if (AttrGetById(node, TidyAttr_BACKGROUND) ||
  505.             AttrGetById(node, TidyAttr_BGCOLOR)    ||
  506.             AttrGetById(node, TidyAttr_TEXT)       ||
  507.             AttrGetById(node, TidyAttr_LINK)       ||
  508.             AttrGetById(node, TidyAttr_VLINK)      ||
  509.             AttrGetById(node, TidyAttr_ALINK))
  510.         {
  511.             doc->badLayout |= USING_BODY;
  512.             return no;
  513.         }
  514.     }
  515.  
  516.     return yes;
  517. }
  518.  
  519. /* create style element using rules from dictionary */
  520. static void CreateStyleElement( TidyDocImpl* doc )
  521. {
  522.     Lexer* lexer = doc->lexer;
  523.     Node *node, *head, *body;
  524.     TagStyle *style;
  525.     AttVal *av;
  526.  
  527.     if ( lexer->styles == NULL && NiceBody(doc) )
  528.         return;
  529.  
  530.     node = NewNode( lexer );
  531.     node->type = StartTag;
  532.     node->implicit = yes;
  533.     node->element = tmbstrdup("style");
  534.     FindTag( doc, node );
  535.  
  536.     /* insert type attribute */
  537.     av = NewAttribute();
  538.     av->attribute = tmbstrdup("type");
  539.     av->value = tmbstrdup("text/css");
  540.     av->delim = '"';
  541.     av->dict = FindAttribute( doc, av );
  542.     node->attributes = av;
  543.  
  544.     body = FindBody( doc );
  545.     lexer->txtstart = lexer->lexsize;
  546.     if ( body )
  547.         CleanBodyAttrs( doc, body );
  548.  
  549.     for (style = lexer->styles; style; style = style->next)
  550.     {
  551.         AddCharToLexer(lexer, ' ');
  552.         AddStringLiteral(lexer, style->tag);
  553.         AddCharToLexer(lexer, '.');
  554.         AddStringLiteral(lexer, style->tag_class);
  555.         AddCharToLexer(lexer, ' ');
  556.         AddCharToLexer(lexer, '{');
  557.         AddStringLiteral(lexer, style->properties);
  558.         AddCharToLexer(lexer, '}');
  559.         AddCharToLexer(lexer, '\n');
  560.     }
  561.  
  562.     lexer->txtend = lexer->lexsize;
  563.  
  564.     InsertNodeAtEnd( node, TextToken(lexer) );
  565.  
  566.     /*
  567.      now insert style element into document head
  568.  
  569.      doc is root node. search its children for html node
  570.      the head node should be first child of html node
  571.     */
  572.     if ( NULL != (head = FindHEAD( doc )) )
  573.         InsertNodeAtEnd( head, node );
  574. }
  575.  
  576.  
  577. /* ensure bidirectional links are consistent */
  578. static void FixNodeLinks(Node *node)
  579. {
  580.     Node *child;
  581.  
  582.     if (node->prev)
  583.         node->prev->next = node;
  584.     else
  585.         node->parent->content = node;
  586.  
  587.     if (node->next)
  588.         node->next->prev = node;
  589.     else
  590.         node->parent->last = node;
  591.  
  592.     for (child = node->content; child; child = child->next)
  593.         child->parent = node;
  594. }
  595.  
  596. /*
  597.  used to strip child of node when
  598.  the node has one and only one child
  599. */
  600. static void StripOnlyChild(TidyDocImpl* doc, Node *node)
  601. {
  602.     Node *child;
  603.  
  604.     child = node->content;
  605.     node->content = child->content;
  606.     node->last = child->last;
  607.     child->content = NULL;
  608.     FreeNode(doc, child);
  609.  
  610.     for (child = node->content; child; child = child->next)
  611.         child->parent = node;
  612. }
  613.  
  614. /* used to strip font start and end tags */
  615. static void DiscardContainer( TidyDocImpl* doc, Node *element, Node **pnode)
  616. {
  617.     Node *node, *parent = element->parent;
  618.  
  619.     if (element->content)
  620.     {
  621.         element->last->next = element->next;
  622.  
  623.         if (element->next)
  624.         {
  625.             element->next->prev = element->last;
  626.             element->last->next = element->next;
  627.         }
  628.         else
  629.             parent->last = element->last;
  630.  
  631.         if (element->prev)
  632.         {
  633.             element->content->prev = element->prev;
  634.             element->prev->next = element->content;
  635.         }
  636.         else
  637.             parent->content = element->content;
  638.  
  639.         for (node = element->content; node; node = node->next)
  640.             node->parent = parent;
  641.  
  642.         *pnode = element->content;
  643.     }
  644.     else
  645.     {
  646.         if (element->next)
  647.             element->next->prev = element->prev;
  648.         else
  649.             parent->last = element->prev;
  650.  
  651.         if (element->prev)
  652.             element->prev->next = element->next;
  653.         else
  654.             parent->content = element->next;
  655.  
  656.         *pnode = element->next;
  657.     }
  658.  
  659.     element->next = element->content = NULL;
  660.     FreeNode(doc, element);
  661. }
  662.  
  663. /*
  664.   Create new string that consists of the
  665.   combined style properties in s1 and s2
  666.  
  667.   To merge property lists, we build a linked
  668.   list of property/values and insert properties
  669.   into the list in order, merging values for
  670.   the same property name.
  671. */
  672. static tmbstr MergeProperties( ctmbstr s1, ctmbstr s2 )
  673. {
  674.     tmbstr s;
  675.     StyleProp *prop;
  676.  
  677.     prop = CreateProps(NULL, s1);
  678.     prop = CreateProps(prop, s2);
  679.     s = CreatePropString(prop);
  680.     FreeStyleProps(prop);
  681.     return s;
  682. }
  683.  
  684. /*
  685.  Add style property to element, creating style
  686.  attribute as needed and adding ; delimiter
  687. */
  688. static void AddStyleProperty(TidyDocImpl* doc, Node *node, ctmbstr property )
  689. {
  690.     AttVal *av = AttrGetById(node, TidyAttr_STYLE);
  691.  
  692.     /* if style attribute already exists then insert property */
  693.  
  694.     if ( av )
  695.     {
  696.         if (av->value != NULL)
  697.         {
  698.             tmbstr s = MergeProperties( av->value, property );
  699.             MemFree( av->value );
  700.             av->value = s;
  701.         }
  702.         else
  703.         {
  704.             av->value = tmbstrdup( property );
  705.         }
  706.     }
  707.     else /* else create new style attribute */
  708.     {
  709.         av = NewAttribute();
  710.         av->attribute = tmbstrdup("style");
  711.         av->value = tmbstrdup(property);
  712.         av->delim = '"';
  713.         av->dict = FindAttribute( doc, av );
  714.         av->next = node->attributes;
  715.         node->attributes = av;
  716.     }
  717. }
  718.  
  719. static void MergeClasses(TidyDocImpl* doc, Node *node, Node *child)
  720. {
  721.     AttVal *av;
  722.     tmbstr s1, s2, names;
  723.  
  724.     for (s2 = NULL, av = child->attributes; av; av = av->next)
  725.     {
  726.         if (attrIsCLASS(av))
  727.         {
  728.             s2 = av->value;
  729.             break;
  730.         }
  731.     }
  732.  
  733.     for (s1 = NULL, av = node->attributes; av; av = av->next)
  734.     {
  735.         if (attrIsCLASS(av))
  736.         {
  737.             s1 = av->value;
  738.             break;
  739.         }
  740.     }
  741.  
  742.     if (s1)
  743.     {
  744.         if (s2)  /* merge class names from both */
  745.         {
  746.             int l1, l2;
  747.             l1 = tmbstrlen(s1);
  748.             l2 = tmbstrlen(s2);
  749.             names = (tmbstr) MemAlloc(l1 + l2 + 2);
  750.             tmbstrcpy(names, s1);
  751.             names[l1] = ' ';
  752.             tmbstrcpy(names+l1+1, s2);
  753.             MemFree(av->value);
  754.             av->value = names;
  755.         }
  756.     }
  757.     else if (s2)  /* copy class names from child */
  758.     {
  759.         av = NewAttribute();
  760.         av->attribute = tmbstrdup("class");
  761.         av->value = tmbstrdup(s2);
  762.         av->delim = '"';
  763.         av->dict = FindAttribute(doc, av);
  764.         av->next = node->attributes;
  765.         node->attributes = av;
  766.     }
  767. }
  768.  
  769. static void MergeStyles(TidyDocImpl* doc, Node *node, Node *child)
  770. {
  771.     AttVal *av;
  772.     tmbstr s1, s2, style;
  773.  
  774.     /*
  775.        the child may have a class attribute used
  776.        for attaching styles, if so the class name
  777.        needs to be copied to node's class
  778.     */
  779.     MergeClasses(doc, node, child);
  780.  
  781.     for (s2 = NULL, av = child->attributes; av; av = av->next)
  782.     {
  783.         if (attrIsSTYLE(av))
  784.         {
  785.             s2 = av->value;
  786.             break;
  787.         }
  788.     }
  789.  
  790.     for (s1 = NULL, av = node->attributes; av; av = av->next)
  791.     {
  792.         if (attrIsSTYLE(av))
  793.         {
  794.             s1 = av->value;
  795.             break;
  796.         }
  797.     }
  798.  
  799.     if (s1)
  800.     {
  801.         if (s2)  /* merge styles from both */
  802.         {
  803.             style = MergeProperties(s1, s2);
  804.             MemFree(av->value);
  805.             av->value = style;
  806.         }
  807.     }
  808.     else if (s2)  /* copy style of child */
  809.     {
  810.         av = NewAttribute();
  811.         av->attribute = tmbstrdup("style");
  812.         av->value = tmbstrdup(s2);
  813.         av->delim = '"';
  814.         av->dict = FindAttribute(doc, av);
  815.         av->next = node->attributes;
  816.         node->attributes = av;
  817.     }
  818. }
  819.  
  820. static ctmbstr FontSize2Name(ctmbstr size, tmbstr buf, size_t count)
  821. {
  822.     static const ctmbstr sizes[7] =
  823.     {
  824.         "60%", "70%", "80%", NULL,
  825.         "120%", "150%", "200%"
  826.     };
  827.  
  828.     if ('0' <= size[0] && size[0] <= '6')
  829.     {
  830.         int n = size[0] - '0';
  831.         return sizes[n];
  832.     }
  833.  
  834.     if (size[0] == '-')
  835.     {
  836.         if ('0' <= size[1] && size[1] <= '6')
  837.         {
  838.             int n = size[1] - '0';
  839.             double x;
  840.  
  841.             for (x = 1; n > 0; --n)
  842.                 x *= 0.8;
  843.  
  844.             x *= 100;
  845.             tmbsnprintf(buf, count, "%d%%", (int)(x));
  846.             return buf;
  847.         }
  848.         return "smaller"; /*"70%"; */
  849.     }
  850.  
  851.     if ('0' <= size[1] && size[1] <= '6')
  852.     {
  853.         int n = size[1] - '0';
  854.         double x;
  855.  
  856.         for (x = 1; n > 0; --n)
  857.             x *= 1.2;
  858.  
  859.         x *= 100;
  860.         tmbsnprintf(buf, count, "%d%%", (int)(x));
  861.         return buf;
  862.     }
  863.  
  864.     return "larger"; /* "140%" */
  865. }
  866.  
  867. static void AddFontFace( TidyDocImpl* doc, Node *node, ctmbstr face )
  868. {
  869.     tmbchar buf[256];
  870.     tmbsnprintf(buf, sizeof(buf), "font-family: %s", face );
  871.     AddStyleProperty( doc, node, buf );
  872. }
  873.  
  874. static void AddFontSize( TidyDocImpl* doc, Node* node, ctmbstr size )
  875. {
  876.     tmbchar work[ 32 ] = {0};
  877.     ctmbstr value = NULL;
  878.  
  879.     if (nodeIsP(node))
  880.     {
  881.         if (tmbstrcmp(size, "6") == 0)
  882.             value = "h1";
  883.         else if (tmbstrcmp(size, "5") == 0)
  884.             value = "h2";
  885.         else if (tmbstrcmp(size, "4") == 0)
  886.             value = "h3";
  887.  
  888.         if (value)
  889.         {
  890.             MemFree(node->element);
  891.             node->element = tmbstrdup(value);
  892.             FindTag(doc, node);
  893.             return;
  894.         }
  895.     }
  896.  
  897.     value = FontSize2Name(size, work, sizeof(work) - 1);
  898.  
  899.     if (value)
  900.     {
  901.         tmbchar buf[64];
  902.         tmbsnprintf(buf, sizeof(buf), "font-size: %s", value);
  903.         AddStyleProperty( doc, node, buf );
  904.     }
  905. }
  906.  
  907. static void AddFontColor( TidyDocImpl* doc, Node *node, ctmbstr color)
  908. {
  909.     tmbchar buf[128];
  910.     tmbsnprintf(buf, sizeof(buf), "color: %s", color);
  911.     AddStyleProperty( doc, node, buf );
  912. }
  913.  
  914. /* force alignment value to lower case */
  915. static void AddAlign( TidyDocImpl* doc, Node *node, ctmbstr align )
  916. {
  917.     tmbchar buf[128], *p;
  918.  
  919.     tmbstrcpy( buf, "text-align: " );
  920.     for ( p = buf + 12; (0 != (*p++ = (tmbchar)ToLower(*align++))); /**/ )
  921.     {
  922.         /**/
  923.     }
  924.     AddStyleProperty( doc, node, buf );
  925. }
  926.  
  927. /*
  928.  add style properties to node corresponding to
  929.  the font face, size and color attributes
  930. */
  931. static void AddFontStyles( TidyDocImpl* doc, Node *node, AttVal *av)
  932. {
  933.     while (av)
  934.     {
  935.         if (attrIsFACE(av))
  936.             AddFontFace( doc, node, av->value );
  937.         else if (attrIsSIZE(av))
  938.             AddFontSize( doc, node, av->value );
  939.         else if (attrIsCOLOR(av))
  940.             AddFontColor( doc, node, av->value );
  941.  
  942.         av = av->next;
  943.     }
  944. }
  945.  
  946. /*
  947.     Symptom: <p align=center>
  948.     Action: <p style="text-align: center">
  949. */
  950. static void TextAlign( TidyDocImpl* doc, Node* node )
  951. {
  952.     AttVal *av, *prev;
  953.  
  954.     prev = NULL;
  955.  
  956.     for (av = node->attributes; av; av = av->next)
  957.     {
  958.         if (attrIsALIGN(av))
  959.         {
  960.             if (prev)
  961.                 prev->next = av->next;
  962.             else
  963.                 node->attributes = av->next;
  964.  
  965.             MemFree(av->attribute);
  966.  
  967.             if (av->value)
  968.             {
  969.                 AddAlign( doc, node, av->value );
  970.                 MemFree(av->value);
  971.             }
  972.  
  973.             MemFree(av);
  974.             break;
  975.         }
  976.  
  977.         prev = av;
  978.     }
  979. }
  980.  
  981. /*
  982.    The clean up rules use the pnode argument to return the
  983.    next node when the original node has been deleted
  984. */
  985.  
  986. /*
  987.     Symptom: <dir> <li> where <li> is only child
  988.     Action: coerce <dir> <li> to <div> with indent.
  989. */
  990.  
  991. static Bool Dir2Div( TidyDocImpl* doc, Node *node, Node **pnode)
  992. {
  993. #pragma unused(pnode)
  994.  
  995.     Node *child;
  996.  
  997.     if ( nodeIsDIR(node) || nodeIsUL(node) || nodeIsOL(node) )
  998.     {
  999.         child = node->content;
  1000.  
  1001.         if (child == NULL)
  1002.             return no;
  1003.  
  1004.         /* check child has no peers */
  1005.  
  1006.         if (child->next)
  1007.             return no;
  1008.  
  1009.         if ( !nodeIsLI(child) )
  1010.             return no;
  1011.  
  1012.         if ( !child->implicit )
  1013.             return no;
  1014.  
  1015.         /* coerce dir to div */
  1016.         node->tag = LookupTagDef( TidyTag_DIV );
  1017.         MemFree( node->element );
  1018.         node->element = tmbstrdup("div");
  1019.         AddStyleProperty( doc, node, "margin-left: 2em" );
  1020.         StripOnlyChild( doc, node );
  1021.         return yes;
  1022.     }
  1023.  
  1024.     return no;
  1025. }
  1026.  
  1027. /*
  1028.     Symptom: <center>
  1029.     Action: replace <center> by <div style="text-align: center">
  1030. */
  1031.  
  1032. static Bool Center2Div( TidyDocImpl* doc, Node *node, Node **pnode)
  1033. {
  1034.     if ( nodeIsCENTER(node) )
  1035.     {
  1036.         if ( cfgBool(doc, TidyDropFontTags) )
  1037.         {
  1038.             if (node->content)
  1039.             {
  1040.                 Node *last = node->last, *parent = node->parent;
  1041.                 DiscardContainer( doc, node, pnode );
  1042.                 node = InferredTag(doc, TidyTag_BR);
  1043.  
  1044.                 if (last->next)
  1045.                     last->next->prev = node;
  1046.  
  1047.                 node->next = last->next;
  1048.                 last->next = node;
  1049.                 node->prev = last;
  1050.  
  1051.                 if (parent->last == last)
  1052.                     parent->last = node;
  1053.  
  1054.                 node->parent = parent;
  1055.             }
  1056.             else
  1057.             {
  1058.                 Node *prev = node->prev, *next = node->next, *parent = node->parent;
  1059.                 DiscardContainer( doc, node, pnode );
  1060.  
  1061.                 node = InferredTag(doc, TidyTag_BR);
  1062.                 node->next = next;
  1063.                 node->prev = prev;
  1064.                 node->parent = parent;
  1065.  
  1066.                 if (next)
  1067.                     next->prev = node;
  1068.                 else
  1069.                     parent->last = node;
  1070.  
  1071.                 if (prev)
  1072.                     prev->next = node;
  1073.                 else
  1074.                     parent->content = node;
  1075.             }
  1076.  
  1077.             return yes;
  1078.         }
  1079.  
  1080.         RenameElem( node, TidyTag_DIV );
  1081.         AddStyleProperty( doc, node, "text-align: center" );
  1082.         return yes;
  1083.     }
  1084.  
  1085.     return no;
  1086. }
  1087.  
  1088. /*
  1089.     Symptom <div><div>...</div></div>
  1090.     Action: merge the two divs
  1091.  
  1092.   This is useful after nested <dir>s used by Word
  1093.   for indenting have been converted to <div>s
  1094. */
  1095. static Bool MergeDivs( TidyDocImpl* doc, Node *node, Node **pnode)
  1096. {
  1097. #pragma unused(pnode)
  1098.  
  1099.     Node *child;
  1100.  
  1101.     if ( !nodeIsDIV(node) )
  1102.         return no;
  1103.  
  1104.     child = node->content;
  1105.  
  1106.     if (!child)
  1107.         return no;
  1108.  
  1109.     if ( !nodeIsDIV(child) )
  1110.         return no;
  1111.  
  1112.     if (child->next != NULL)
  1113.         return no;
  1114.  
  1115.     MergeStyles( doc, node, child );
  1116.     StripOnlyChild( doc, node );
  1117.     return yes;
  1118. }
  1119.  
  1120. /*
  1121.     Symptom: <ul><li><ul>...</ul></li></ul>
  1122.     Action: discard outer list
  1123. */
  1124.  
  1125. static Bool NestedList( TidyDocImpl* doc, Node *node, Node **pnode )
  1126. {
  1127.     Node *child, *list;
  1128.  
  1129.     if ( nodeIsUL(node) || nodeIsOL(node) )
  1130.     {
  1131.         child = node->content;
  1132.  
  1133.         if (child == NULL)
  1134.             return no;
  1135.  
  1136.         /* check child has no peers */
  1137.  
  1138.         if (child->next)
  1139.             return no;
  1140.  
  1141.         list = child->content;
  1142.  
  1143.         if (!list)
  1144.             return no;
  1145.  
  1146.         if (list->tag != node->tag)
  1147.             return no;
  1148.  
  1149.         *pnode = list;  /* Set node to resume iteration */
  1150.  
  1151.         /* move inner list node into position of outer node */
  1152.         list->prev = node->prev;
  1153.         list->next = node->next;
  1154.         list->parent = node->parent;
  1155.         FixNodeLinks(list);
  1156.  
  1157.         /* get rid of outer ul and its li */
  1158.         /* XXX: Are we leaking the child node? -creitzel 7 Jun, 01 */
  1159.         child->content = NULL;
  1160.         node->content = NULL;
  1161.         node->next = NULL;
  1162.         FreeNode( doc, node );
  1163.         node = NULL;
  1164.  
  1165.         /*
  1166.           If prev node was a list the chances are this node
  1167.           should be appended to that list. Word has no way of
  1168.           recognizing nested lists and just uses indents
  1169.         */
  1170.  
  1171.         if (list->prev)
  1172.         {
  1173.             if ( nodeIsUL(list->prev) || nodeIsOL(list->prev) )
  1174.             {
  1175.                 node = list;
  1176.                 list = node->prev;
  1177.                 list->next = node->next;
  1178.  
  1179.                 if (list->next)
  1180.                     list->next->prev = list;
  1181.  
  1182.                 child = list->last;  /* <li> */
  1183.  
  1184.                 node->parent = child;
  1185.                 node->next = NULL;
  1186.                 node->prev = child->last;
  1187.                 FixNodeLinks(node);
  1188.                 CleanNode( doc, node );
  1189.             }
  1190.         }
  1191.  
  1192.         return yes;
  1193.     }
  1194.  
  1195.     return no;
  1196. }
  1197.  
  1198. /*
  1199.   Symptom: the only child of a block-level element is a
  1200.   presentation element such as B, I or FONT
  1201.  
  1202.   Action: add style "font-weight: bold" to the block and
  1203.   strip the <b> element, leaving its children.
  1204.  
  1205.   example:
  1206.  
  1207.     <p>
  1208.       <b><font face="Arial" size="6">Draft Recommended Practice</font></b>
  1209.     </p>
  1210.  
  1211.   becomes:
  1212.  
  1213.       <p style="font-weight: bold; font-family: Arial; font-size: 6">
  1214.         Draft Recommended Practice
  1215.       </p>
  1216.  
  1217.   This code also replaces the align attribute by a style attribute.
  1218.   However, to avoid CSS problems with Navigator 4, this isn't done
  1219.   for the elements: caption, tr and table
  1220. */
  1221. static Bool BlockStyle( TidyDocImpl* doc, Node *node, Node **pnode )
  1222. {
  1223. #pragma unused(pnode)
  1224.  
  1225.     Node *child;
  1226.  
  1227.     if (node->tag->model & (CM_BLOCK | CM_LIST | CM_DEFLIST | CM_TABLE))
  1228.     {
  1229.         if ( !nodeIsTABLE(node) && !nodeIsTR(node) && !nodeIsLI(node) )
  1230.         {
  1231.             /* check for align attribute */
  1232.             if ( !nodeIsCAPTION(node) )
  1233.                 TextAlign( doc, node );
  1234.  
  1235.             child = node->content;
  1236.             if (child == NULL)
  1237.                 return no;
  1238.  
  1239.             /* check child has no peers */
  1240.             if (child->next)
  1241.                 return no;
  1242.  
  1243.             if ( nodeIsB(child) )
  1244.             {
  1245.                 MergeStyles( doc, node, child );
  1246.                 AddStyleProperty( doc, node, "font-weight: bold" );
  1247.                 StripOnlyChild( doc, node );
  1248.                 return yes;
  1249.             }
  1250.  
  1251.             if ( nodeIsI(child) )
  1252.             {
  1253.                 MergeStyles( doc, node, child );
  1254.                 AddStyleProperty( doc, node, "font-style: italic" );
  1255.                 StripOnlyChild( doc, node );
  1256.                 return yes;
  1257.             }
  1258.  
  1259.             if ( nodeIsFONT(child) )
  1260.             {
  1261.                 MergeStyles( doc, node, child );
  1262.                 AddFontStyles( doc, node, child->attributes );
  1263.                 StripOnlyChild( doc, node );
  1264.                 return yes;
  1265.             }
  1266.         }
  1267.     }
  1268.  
  1269.     return no;
  1270. }
  1271.  
  1272. /* the only child of table cell or an inline element such as em */
  1273. static Bool InlineStyle( TidyDocImpl* doc, Node *node, Node **pnode )
  1274. {
  1275. #pragma unused(pnode)
  1276.  
  1277.     Node *child;
  1278.  
  1279.     if ( !nodeIsFONT(node) && nodeHasCM(node, CM_INLINE|CM_ROW) )
  1280.     {
  1281.         child = node->content;
  1282.  
  1283.         if (child == NULL)
  1284.             return no;
  1285.  
  1286.         /* check child has no peers */
  1287.  
  1288.         if (child->next)
  1289.             return no;
  1290.  
  1291.         if ( nodeIsB(child) && cfgBool(doc, TidyLogicalEmphasis) )
  1292.         {
  1293.             MergeStyles( doc, node, child );
  1294.             AddStyleProperty( doc, node, "font-weight: bold" );
  1295.             StripOnlyChild( doc, node );
  1296.             return yes;
  1297.         }
  1298.  
  1299.         if ( nodeIsI(child) && cfgBool(doc, TidyLogicalEmphasis) )
  1300.         {
  1301.             MergeStyles( doc, node, child );
  1302.             AddStyleProperty( doc, node, "font-style: italic" );
  1303.             StripOnlyChild( doc, node );
  1304.             return yes;
  1305.         }
  1306.  
  1307.         if ( nodeIsFONT(child) )
  1308.         {
  1309.             MergeStyles( doc, node, child );
  1310.             AddFontStyles( doc, node, child->attributes );
  1311.             StripOnlyChild( doc, node );
  1312.             return yes;
  1313.         }
  1314.     }
  1315.  
  1316.     return no;
  1317. }
  1318.  
  1319. /*
  1320.   Replace font elements by span elements, deleting
  1321.   the font element's attributes and replacing them
  1322.   by a single style attribute.
  1323. */
  1324. static Bool Font2Span( TidyDocImpl* doc, Node *node, Node **pnode )
  1325. {
  1326.     AttVal *av, *style, *next;
  1327.  
  1328.     if ( nodeIsFONT(node) )
  1329.     {
  1330.         if ( cfgBool(doc, TidyDropFontTags) )
  1331.         {
  1332.             DiscardContainer( doc, node, pnode );
  1333.             return yes;
  1334.         }
  1335.  
  1336.         /* if FONT is only child of parent element then leave alone */
  1337.         if ( node->parent->content == node && node->next == NULL )
  1338.             return no;
  1339.  
  1340.         AddFontStyles( doc, node, node->attributes );
  1341.  
  1342.         /* extract style attribute and free the rest */
  1343.         av = node->attributes;
  1344.         style = NULL;
  1345.  
  1346.         while (av)
  1347.         {
  1348.             next = av->next;
  1349.  
  1350.             if (attrIsSTYLE(av))
  1351.             {
  1352.                 av->next = NULL;
  1353.                 style = av;
  1354.             }
  1355.             else
  1356.             {
  1357.                 FreeAttribute( doc, av );
  1358.             }
  1359.             av = next;
  1360.         }
  1361.  
  1362.         node->attributes = style;
  1363.         RenameElem( node, TidyTag_SPAN );
  1364.         return yes;
  1365.     }
  1366.  
  1367.     return no;
  1368. }
  1369.  
  1370. static Bool IsElement(Node *node)
  1371. {
  1372.     return (node->type == StartTag || node->type == StartEndTag ? yes : no);
  1373. }
  1374.  
  1375. /*
  1376.   Applies all matching rules to a node.
  1377. */
  1378. Node* CleanNode( TidyDocImpl* doc, Node *node )
  1379. {
  1380.     Node *next = NULL;
  1381.  
  1382.     for (next = node; node && IsElement(node); node = next)
  1383.     {
  1384.         if ( Dir2Div(doc, node, &next) )
  1385.             continue;
  1386.  
  1387.         /* Special case: true result means
  1388.         ** that arg node and its parent no longer exist.
  1389.         ** So we must jump back up the CreateStyleProperties()
  1390.         ** call stack until we have a valid node reference.
  1391.         */
  1392.         if ( NestedList(doc, node, &next) )
  1393.             return next;
  1394.  
  1395.         if ( Center2Div(doc, node, &next) )
  1396.             continue;
  1397.  
  1398.         if (cfgBool(doc, TidyMergeDivs) && MergeDivs(doc, node, &next))
  1399.             continue;
  1400.  
  1401.         if ( BlockStyle(doc, node, &next) )
  1402.             continue;
  1403.  
  1404.         if ( InlineStyle(doc, node, &next) )
  1405.             continue;
  1406.  
  1407.         if ( Font2Span(doc, node, &next) )
  1408.             continue;
  1409.  
  1410.         break;
  1411.     }
  1412.  
  1413.     return next;
  1414. }
  1415.  
  1416. /* Special case: if the current node is destroyed by
  1417. ** CleanNode() lower in the tree, this node and its parent
  1418. ** no longer exist.  So we must jump back up the CleanTree()
  1419. ** call stack until we have a valid node reference.
  1420. */
  1421.  
  1422. static Node* CleanTree( TidyDocImpl* doc, Node *node )
  1423. {
  1424.     if (node->content)
  1425.     {
  1426.         Node *child;
  1427.         for (child = node->content; child != NULL; child = child->next)
  1428.         {
  1429.             child = CleanTree( doc, child );
  1430.             if ( !child )
  1431.                 break;
  1432.         }
  1433.     }
  1434.  
  1435.     return CleanNode( doc, node );
  1436. }
  1437.  
  1438. static void DefineStyleRules( TidyDocImpl* doc, Node *node )
  1439. {
  1440.     Node *child;
  1441.  
  1442.     if (node->content)
  1443.     {
  1444.         for (child = node->content;
  1445.                 child != NULL; child = child->next)
  1446.         {
  1447.             DefineStyleRules( doc, child );
  1448.         }
  1449.     }
  1450.  
  1451.     Style2Rule( doc, node );
  1452. }
  1453.  
  1454. void CleanDocument( TidyDocImpl* doc )
  1455. {
  1456.     /* placeholder.  CleanTree()/CleanNode() will not
  1457.     ** zap root element 
  1458.     */
  1459.     CleanTree( doc, &doc->root );
  1460.  
  1461.     if ( cfgBool(doc, TidyMakeClean) )
  1462.     {
  1463.         DefineStyleRules( doc, &doc->root );
  1464.         CreateStyleElement( doc );
  1465.     }
  1466. }
  1467.  
  1468. /* simplifies <b><b> ... </b> ...</b> etc. */
  1469. void NestedEmphasis( TidyDocImpl* doc, Node* node )
  1470. {
  1471.     Node *next;
  1472.  
  1473.     while (node)
  1474.     {
  1475.         next = node->next;
  1476.  
  1477.         if ( (nodeIsB(node) || nodeIsI(node))
  1478.              && node->parent && node->parent->tag == node->tag)
  1479.         {
  1480.             /* strip redundant inner element */
  1481.             DiscardContainer( doc, node, &next );
  1482.             node = next;
  1483.             continue;
  1484.         }
  1485.  
  1486.         if ( node->content )
  1487.             NestedEmphasis( doc, node->content );
  1488.  
  1489.         node = next;
  1490.     }
  1491. }
  1492.  
  1493.  
  1494.  
  1495. /* replace i by em and b by strong */
  1496. void EmFromI( TidyDocImpl* doc, Node* node )
  1497. {
  1498.     while (node)
  1499.     {
  1500.         if ( nodeIsI(node) )
  1501.             RenameElem( node, TidyTag_EM );
  1502.         else if ( nodeIsB(node) )
  1503.             RenameElem( node, TidyTag_STRONG );
  1504.  
  1505.         if ( node->content )
  1506.             EmFromI( doc, node->content );
  1507.  
  1508.         node = node->next;
  1509.     }
  1510. }
  1511.  
  1512. static Bool HasOneChild(Node *node)
  1513. {
  1514.     return (node->content && node->content->next == NULL);
  1515. }
  1516.  
  1517. /*
  1518.  Some people use dir or ul without an li
  1519.  to indent the content. The pattern to
  1520.  look for is a list with a single implicit
  1521.  li. This is recursively replaced by an
  1522.  implicit blockquote.
  1523. */
  1524. void List2BQ( TidyDocImpl* doc, Node* node )
  1525. {
  1526.     while (node)
  1527.     {
  1528.         if (node->content)
  1529.             List2BQ( doc, node->content );
  1530.  
  1531.         if ( node->tag && node->tag->parser == ParseList &&
  1532.              HasOneChild(node) && node->content->implicit )
  1533.         {
  1534.             StripOnlyChild( doc, node );
  1535.             RenameElem( node, TidyTag_BLOCKQUOTE );
  1536.             node->implicit = yes;
  1537.         }
  1538.  
  1539.         node = node->next;
  1540.     }
  1541. }
  1542.  
  1543.  
  1544. /*
  1545.  Replace implicit blockquote by div with an indent
  1546.  taking care to reduce nested blockquotes to a single
  1547.  div with the indent set to match the nesting depth
  1548. */
  1549. void BQ2Div( TidyDocImpl* doc, Node *node )
  1550. {
  1551.     tmbchar indent_buf[ 32 ];
  1552.     int indent;
  1553.     uint len;
  1554.     AttVal *attval;
  1555.  
  1556.     while (node)
  1557.     {
  1558.         if ( nodeIsBLOCKQUOTE(node) && node->implicit )
  1559.         {
  1560.             indent = 1;
  1561.  
  1562.             while( HasOneChild(node) &&
  1563.                    nodeIsBLOCKQUOTE(node->content) &&
  1564.                    node->implicit)
  1565.             {
  1566.                 ++indent;
  1567.                 StripOnlyChild( doc, node );
  1568.             }
  1569.  
  1570.             if (node->content)
  1571.                 BQ2Div( doc, node->content );
  1572.  
  1573.             len = tmbsnprintf(indent_buf, sizeof(indent_buf), "margin-left: %dem", 2*indent);
  1574.  
  1575.             RenameElem( node, TidyTag_DIV );
  1576.  
  1577.             attval = AttrGetById(node, TidyAttr_STYLE);
  1578.             if (attval)
  1579.             {
  1580.                 tmbstr s = (tmbstr) MemAlloc(len + 3 + tmbstrlen(attval->value));
  1581.                 tmbstrcpy(s, indent_buf);
  1582.                 tmbstrcat(s, "; ");
  1583.                 tmbstrcat(s, attval->value);
  1584.  
  1585.                 MemFree(attval->value);
  1586.                 attval->value = s;
  1587.             }
  1588.             else
  1589.             {
  1590.                 AddAttribute( doc, node, "style", indent_buf );
  1591.             }
  1592.         }
  1593.         else if (node->content)
  1594.             BQ2Div( doc, node->content );
  1595.  
  1596.         node = node->next;
  1597.     }
  1598. }
  1599.  
  1600.  
  1601. Node* FindEnclosingCell( TidyDocImpl* doc, Node *node)
  1602. {
  1603. #pragma unused(doc)
  1604.  
  1605.     Node *check;
  1606.  
  1607.     for ( check=node; check; check = check->parent )
  1608.     {
  1609.       if ( nodeIsTD(check) )
  1610.         return check;
  1611.     }
  1612.     return NULL;
  1613. }
  1614.  
  1615. /* node is <![if ...]> prune up to <![endif]> */
  1616. static Node* PruneSection( TidyDocImpl* doc, Node *node )
  1617. {
  1618.     Lexer* lexer = doc->lexer;
  1619.  
  1620.     for (;;)
  1621.     {
  1622.         ctmbstr lexbuf = lexer->lexbuf + node->start;
  1623.         if ( tmbstrncmp(lexbuf, "if !supportEmptyParas", 21) == 0 )
  1624.         {
  1625.           Node* cell = FindEnclosingCell( doc, node );
  1626.           if ( cell )
  1627.           {
  1628.             /* Need to put   into cell so it doesn't look weird
  1629.             */
  1630.             Node* nbsp = NewLiteralTextNode( lexer, "\240" );
  1631.             assert( (byte)'\240' == (byte)160 );
  1632.             InsertNodeBeforeElement( node, nbsp );
  1633.           }
  1634.         }
  1635.  
  1636.         /* discard node and returns next */
  1637.         node = DiscardElement( doc, node );
  1638.  
  1639.         if (node == NULL)
  1640.             return NULL;
  1641.         
  1642.         if (node->type == SectionTag)
  1643.         {
  1644.             if (tmbstrncmp(lexer->lexbuf + node->start, "if", 2) == 0)
  1645.             {
  1646.                 node = PruneSection( doc, node );
  1647.                 continue;
  1648.             }
  1649.  
  1650.             if (tmbstrncmp(lexer->lexbuf + node->start, "endif", 5) == 0)
  1651.             {
  1652.                 node = DiscardElement( doc, node );
  1653.                 break;
  1654.             }
  1655.         }
  1656.     }
  1657.  
  1658.     return node;
  1659. }
  1660.  
  1661. void DropSections( TidyDocImpl* doc, Node* node )
  1662. {
  1663.     Lexer* lexer = doc->lexer;
  1664.     while (node)
  1665.     {
  1666.         if (node->type == SectionTag)
  1667.         {
  1668.             /* prune up to matching endif */
  1669.             if ((tmbstrncmp(lexer->lexbuf + node->start, "if", 2) == 0) &&
  1670.                 (tmbstrncmp(lexer->lexbuf + node->start, "if !vml", 7) != 0)) /* #444394 - fix 13 Sep 01 */
  1671.             {
  1672.                 node = PruneSection( doc, node );
  1673.                 continue;
  1674.             }
  1675.  
  1676.             /* discard others as well */
  1677.             node = DiscardElement( doc, node );
  1678.             continue;
  1679.         }
  1680.  
  1681.         if (node->content)
  1682.             DropSections( doc, node->content );
  1683.  
  1684.         node = node->next;
  1685.     }
  1686. }
  1687.  
  1688. static void PurgeWord2000Attributes( TidyDocImpl* doc, Node* node )
  1689. {
  1690. #pragma unused(doc)
  1691.  
  1692.     AttVal *attr, *next, *prev = NULL;
  1693.  
  1694.     for ( attr = node->attributes; attr; attr = next )
  1695.     {
  1696.         next = attr->next;
  1697.  
  1698.         /* special check for class="Code" denoting pre text */
  1699.         /* Pass thru user defined styles as HTML class names */
  1700.         if (attrIsCLASS(attr))
  1701.         {
  1702.             if (AttrValueIs(attr, "Code") ||
  1703.                  tmbstrncmp(attr->value, "Mso", 3) != 0 )
  1704.             {
  1705.                 prev = attr;
  1706.                 continue;
  1707.             }
  1708.         }
  1709.  
  1710.         if (attrIsCLASS(attr) ||
  1711.             attrIsSTYLE(attr) ||
  1712.             attrIsLANG(attr)  ||
  1713.              ( (attrIsHEIGHT(attr) || attrIsWIDTH(attr)) &&
  1714.                (nodeIsTD(node) || nodeIsTR(node) || nodeIsTH(node)) ) ||
  1715.              (attr->attribute && tmbstrncmp(attr->attribute, "x:", 2) == 0) )
  1716.         {
  1717.             if (prev)
  1718.                 prev->next = next;
  1719.             else
  1720.                 node->attributes = next;
  1721.  
  1722.             FreeAttribute( doc, attr );
  1723.         }
  1724.         else
  1725.             prev = attr;
  1726.     }
  1727. }
  1728.  
  1729. /* Word2000 uses span excessively, so we strip span out */
  1730. static Node* StripSpan( TidyDocImpl* doc, Node* span )
  1731. {
  1732.     Node *node, *prev = NULL, *content;
  1733.  
  1734.     /*
  1735.      deal with span elements that have content
  1736.      by splicing the content in place of the span
  1737.      after having processed it
  1738.     */
  1739.  
  1740.     CleanWord2000( doc, span->content );
  1741.     content = span->content;
  1742.  
  1743.     if (span->prev)
  1744.         prev = span->prev;
  1745.     else if (content)
  1746.     {
  1747.         node = content;
  1748.         content = content->next;
  1749.         RemoveNode(node);
  1750.         InsertNodeBeforeElement(span, node);
  1751.         prev = node;
  1752.     }
  1753.  
  1754.     while (content)
  1755.     {
  1756.         node = content;
  1757.         content = content->next;
  1758.         RemoveNode(node);
  1759.         InsertNodeAfterElement(prev, node);
  1760.         prev = node;
  1761.     }
  1762.  
  1763.     if (span->next == NULL)
  1764.         span->parent->last = prev;
  1765.  
  1766.     node = span->next;
  1767.     span->content = NULL;
  1768.     DiscardElement( doc, span );
  1769.     return node;
  1770. }
  1771.  
  1772. /* map non-breaking spaces to regular spaces */
  1773. void NormalizeSpaces(Lexer *lexer, Node *node)
  1774. {
  1775.     while ( node )
  1776.     {
  1777.         if ( node->content )
  1778.             NormalizeSpaces( lexer, node->content );
  1779.  
  1780.         if (node->type == TextNode)
  1781.         {
  1782.             uint i, c;
  1783.             tmbstr p = lexer->lexbuf + node->start;
  1784.  
  1785.             for (i = node->start; i < node->end; ++i)
  1786.             {
  1787.                 c = (byte) lexer->lexbuf[i];
  1788.  
  1789.                 /* look for UTF-8 multibyte character */
  1790.                 if ( c > 0x7F )
  1791.                     i += GetUTF8( lexer->lexbuf + i, &c );
  1792.  
  1793.                 if ( c == 160 )
  1794.                     c = ' ';
  1795.  
  1796.                 p = PutUTF8(p, c);
  1797.             }
  1798.             node->end = p - lexer->lexbuf;
  1799.         }
  1800.  
  1801.         node = node->next;
  1802.     }
  1803. }
  1804.  
  1805. /* used to hunt for hidden preformatted sections */
  1806. Bool NoMargins(Node *node)
  1807. {
  1808.     AttVal *attval = AttrGetById(node, TidyAttr_STYLE);
  1809.  
  1810.     if ( !AttrHasValue(attval) )
  1811.         return no;
  1812.  
  1813.     /* search for substring "margin-top: 0" */
  1814.     if (!tmbsubstr(attval->value, "margin-top: 0"))
  1815.         return no;
  1816.  
  1817.     /* search for substring "margin-bottom: 0" */
  1818.     if (!tmbsubstr(attval->value, "margin-bottom: 0"))
  1819.         return no;
  1820.  
  1821.     return yes;
  1822. }
  1823.  
  1824. /* does element have a single space as its content? */
  1825. static Bool SingleSpace( Lexer* lexer, Node* node )
  1826. {
  1827.     if ( node->content )
  1828.     {
  1829.         node = node->content;
  1830.  
  1831.         if ( node->next != NULL )
  1832.             return no;
  1833.  
  1834.         if ( node->type != TextNode )
  1835.             return no;
  1836.  
  1837.         if ( (node->end - node->start) == 1 &&
  1838.              lexer->lexbuf[node->start] == ' ' )
  1839.             return yes;
  1840.  
  1841.         if ( (node->end - node->start) == 2 )
  1842.         {
  1843.             uint c = 0;
  1844.             GetUTF8( lexer->lexbuf + node->start, &c );
  1845.             if ( c == 160 )
  1846.                 return yes;
  1847.         }
  1848.     }
  1849.  
  1850.     return no;
  1851. }
  1852.  
  1853. /*
  1854.  This is a major clean up to strip out all the extra stuff you get
  1855.  when you save as web page from Word 2000. It doesn't yet know what
  1856.  to do with VML tags, but these will appear as errors unless you
  1857.  declare them as new tags, such as o:p which needs to be declared
  1858.  as inline.
  1859. */
  1860. void CleanWord2000( TidyDocImpl* doc, Node *node)
  1861. {
  1862.     /* used to a list from a sequence of bulletted p's */
  1863.     Lexer* lexer = doc->lexer;
  1864.     Node* list = NULL;
  1865.  
  1866.     while ( node )
  1867.     {
  1868.         /* get rid of Word's xmlns attributes */
  1869.         if ( nodeIsHTML(node) )
  1870.         {
  1871.             /* check that it's a Word 2000 document */
  1872.             if ( !GetAttrByName(node, "xmlns:o") &&
  1873.                  !cfgBool(doc, TidyMakeBare) )
  1874.                 return;
  1875.  
  1876.             FreeAttrs( doc, node );
  1877.         }
  1878.  
  1879.         /* fix up preformatted sections by looking for a
  1880.         ** sequence of paragraphs with zero top/bottom margin
  1881.         */
  1882.         if ( nodeIsP(node) )
  1883.         {
  1884.             if (NoMargins(node))
  1885.             {
  1886.                 Node *pre, *next;
  1887.                 CoerceNode(doc, node, TidyTag_PRE, no, yes);
  1888.  
  1889.                 PurgeWord2000Attributes( doc, node );
  1890.  
  1891.                 if (node->content)
  1892.                     CleanWord2000( doc, node->content );
  1893.  
  1894.                 pre = node;
  1895.                 node = node->next;
  1896.  
  1897.                 /* continue to strip p's */
  1898.  
  1899.                 while ( nodeIsP(node) && NoMargins(node) )
  1900.                 {
  1901.                     next = node->next;
  1902.                     RemoveNode(node);
  1903.                     InsertNodeAtEnd(pre, NewLineNode(lexer));
  1904.                     InsertNodeAtEnd(pre, node);
  1905.                     StripSpan( doc, node );
  1906.                     node = next;
  1907.                 }
  1908.  
  1909.                 if (node == NULL)
  1910.                     break;
  1911.             }
  1912.         }
  1913.  
  1914.         if (node->tag && (node->tag->model & CM_BLOCK)
  1915.             && SingleSpace(lexer, node))
  1916.         {
  1917.             node = StripSpan( doc, node );
  1918.             continue;
  1919.         }
  1920.         /* discard Word's style verbiage */
  1921.         if ( nodeIsSTYLE(node) || nodeIsMETA(node) ||
  1922.              node->type == CommentTag )
  1923.         {
  1924.             node = DiscardElement( doc, node );
  1925.             continue;
  1926.         }
  1927.  
  1928.         /* strip out all span and font tags Word scatters so liberally! */
  1929.         if ( nodeIsSPAN(node) || nodeIsFONT(node) )
  1930.         {
  1931.             node = StripSpan( doc, node );
  1932.             continue;
  1933.         }
  1934.  
  1935.         if ( nodeIsLINK(node) )
  1936.         {
  1937.             AttVal *attr = AttrGetById(node, TidyAttr_REL);
  1938.  
  1939.             if (AttrValueIs(attr, "File-List"))
  1940.             {
  1941.                 node = DiscardElement( doc, node );
  1942.                 continue;
  1943.             }
  1944.         }
  1945.  
  1946.         /* discard empty paragraphs */
  1947.  
  1948.         if ( node->content == NULL && nodeIsP(node) )
  1949.         {
  1950.             /*  Use the existing function to ensure consistency */
  1951.             node = TrimEmptyElement( doc, node );
  1952.             continue;
  1953.         }
  1954.  
  1955.         if ( nodeIsP(node) )
  1956.         {
  1957.             AttVal *attr, *atrStyle;
  1958.             
  1959.             attr = AttrGetById(node, TidyAttr_CLASS);
  1960.             atrStyle = AttrGetById(node, TidyAttr_STYLE);
  1961.             /*
  1962.                (JES) Sometimes Word marks a list item with the following hokie syntax
  1963.                <p class="MsoNormal" style="...;mso-list:l1 level1 lfo1;
  1964.                 translate these into <li>
  1965.             */
  1966.             /* map sequence of <p class="MsoListBullet"> to <ul>...</ul> */
  1967.             /* map <p class="MsoListNumber"> to <ol>...</ol> */
  1968.             if ( AttrMatches(attr, "MsoListBullet") ||
  1969.                  AttrMatches(attr, "MsoListNumber") ||
  1970.                  AttrContains(atrStyle, "mso-list:") )
  1971.             {
  1972.                 TidyTagId listType = TidyTag_UL;
  1973.                 if (AttrValueIs(attr, "MsoListNumber"))
  1974.                     listType = TidyTag_OL;
  1975.  
  1976.                 CoerceNode(doc, node, TidyTag_LI, no, yes);
  1977.  
  1978.                 if ( !list || TagId(list) != listType )
  1979.                 {
  1980.                     const Dict* tag = LookupTagDef( listType );
  1981.                     list = InferredTag(doc, tag->id);
  1982.                     InsertNodeBeforeElement(node, list);
  1983.                 }
  1984.  
  1985.                 PurgeWord2000Attributes( doc, node );
  1986.  
  1987.                 if ( node->content )
  1988.                     CleanWord2000( doc, node->content );
  1989.  
  1990.                 /* remove node and append to contents of list */
  1991.                 RemoveNode(node);
  1992.                 InsertNodeAtEnd(list, node);
  1993.                 node = list;
  1994.             }
  1995.             /* map sequence of <p class="Code"> to <pre>...</pre> */
  1996.             else if (AttrValueIs(attr, "Code"))
  1997.             {
  1998.                 Node *br = NewLineNode(lexer);
  1999.                 NormalizeSpaces(lexer, node);
  2000.  
  2001.                 if ( !list || TagId(list) != TidyTag_PRE )
  2002.                 {
  2003.                     list = InferredTag(doc, TidyTag_PRE);
  2004.                     InsertNodeBeforeElement(node, list);
  2005.                 }
  2006.  
  2007.                 /* remove node and append to contents of list */
  2008.                 RemoveNode(node);
  2009.                 InsertNodeAtEnd(list, node);
  2010.                 StripSpan( doc, node );
  2011.                 InsertNodeAtEnd(list, br);
  2012.                 node = list->next;
  2013.             }
  2014.             else
  2015.                 list = NULL;
  2016.         }
  2017.         else
  2018.             list = NULL;
  2019.  
  2020.         if (!node)
  2021.             return;
  2022.  
  2023.         /* strip out style and class attributes */
  2024.         if (node->type == StartTag || node->type == StartEndTag)
  2025.             PurgeWord2000Attributes( doc, node );
  2026.  
  2027.         if (node->content)
  2028.             CleanWord2000( doc, node->content );
  2029.  
  2030.         node = node->next;
  2031.     }
  2032. }
  2033.  
  2034. Bool IsWord2000( TidyDocImpl* doc )
  2035. {
  2036.     AttVal *attval;
  2037.     Node *node, *head;
  2038.     Node *html = FindHTML( doc );
  2039.  
  2040.     if (html && GetAttrByName(html, "xmlns:o"))
  2041.         return yes;
  2042.     
  2043.     /* search for <meta name="GENERATOR" content="Microsoft ..."> */
  2044.     head = FindHEAD( doc );
  2045.  
  2046.     if (head)
  2047.     {
  2048.         for (node = head->content; node; node = node->next)
  2049.         {
  2050.             if ( !nodeIsMETA(node) )
  2051.                 continue;
  2052.  
  2053.             attval = AttrGetById( node, TidyAttr_NAME );
  2054.  
  2055.             if ( !AttrMatches(attval, "generator") )
  2056.                 continue;
  2057.  
  2058.             attval =  AttrGetById( node, TidyAttr_CONTENT );
  2059.  
  2060.             if ( AttrContains(attval, "Microsoft") )
  2061.                 return yes;
  2062.         }
  2063.     }
  2064.  
  2065.     return no;
  2066. }
  2067.  
  2068. /* where appropriate move object elements from head to body */
  2069. void BumpObject( TidyDocImpl* doc, Node *html )
  2070. {
  2071.     Node *node, *next, *head = NULL, *body = NULL;
  2072.  
  2073.     if (!html)
  2074.         return;
  2075.  
  2076.     for ( node = html->content; node != NULL; node = node->next )
  2077.     {
  2078.         if ( nodeIsHEAD(node) )
  2079.             head = node;
  2080.  
  2081.         if ( nodeIsBODY(node) )
  2082.             body = node;
  2083.     }
  2084.  
  2085.     if ( head != NULL && body != NULL )
  2086.     {
  2087.         for (node = head->content; node != NULL; node = next)
  2088.         {
  2089.             next = node->next;
  2090.  
  2091.             if ( nodeIsOBJECT(node) )
  2092.             {
  2093.                 Node *child;
  2094.                 Bool bump = no;
  2095.  
  2096.                 for (child = node->content; child != NULL; child = child->next)
  2097.                 {
  2098.                     /* bump to body unless content is param */
  2099.                     if ( (child->type == TextNode && !IsBlank(doc->lexer, node))
  2100.                          || !nodeIsPARAM(child) )
  2101.                     {
  2102.                             bump = yes;
  2103.                             break;
  2104.                     }
  2105.                 }
  2106.  
  2107.                 if ( bump )
  2108.                 {
  2109.                     RemoveNode( node );
  2110.                     InsertNodeAtStart( body, node );
  2111.                 }
  2112.             }
  2113.         }
  2114.     }
  2115. }
  2116.  
  2117. /* This is disabled due to http://tidy.sf.net/bug/681116 */
  2118. #if 0
  2119. void FixBrakes( TidyDocImpl* pDoc, Node *pParent )
  2120. {
  2121.     Node *pNode;
  2122.     Bool bBRDeleted = no;
  2123.  
  2124.     if (NULL == pParent)
  2125.         return;
  2126.  
  2127.     /*  First, check the status of All My Children  */
  2128.     pNode = pParent->content;
  2129.     while (NULL != pNode )
  2130.     {
  2131.         /* The node may get trimmed, so save the next pointer, if any */
  2132.         Node *pNext = pNode->next;
  2133.         FixBrakes( pDoc, pNode );
  2134.         pNode = pNext;
  2135.     }
  2136.  
  2137.  
  2138.     /*  As long as my last child is a <br />, move it to my last peer  */
  2139.     if ( nodeCMIsBlock( pParent ))
  2140.     { 
  2141.         for ( pNode = pParent->last; 
  2142.               NULL != pNode && nodeIsBR( pNode ); 
  2143.               pNode = pParent->last ) 
  2144.         {
  2145.             if ( NULL == pNode->attributes && no == bBRDeleted )
  2146.             {
  2147.                 DiscardElement( pDoc, pNode );
  2148.                 bBRDeleted = yes;
  2149.             }
  2150.             else
  2151.             {
  2152.                 RemoveNode( pNode );
  2153.                 InsertNodeAfterElement( pParent, pNode );
  2154.             }
  2155.         }
  2156.         TrimEmptyElement( pDoc, pParent );
  2157.     }
  2158. }
  2159. #endif
  2160.  
  2161. void VerifyHTTPEquiv(TidyDocImpl* pDoc, Node *head)
  2162. {
  2163.     Node *pNode;
  2164.     StyleProp *pFirstProp = NULL, *pLastProp = NULL, *prop = NULL;
  2165.     tmbstr s, pszBegin, pszEnd;
  2166.     ctmbstr enc = GetEncodingNameFromTidyId(cfg(pDoc, TidyOutCharEncoding));
  2167.  
  2168.     if (!enc)
  2169.         return;
  2170.  
  2171.     if (!nodeIsHEAD(head))
  2172.         head = FindHEAD(pDoc);
  2173.  
  2174.     if (!head)
  2175.         return;
  2176.  
  2177.     /* Find any <meta http-equiv='Content-Type' content='...' /> */
  2178.     for (pNode = head->content; NULL != pNode; pNode = pNode->next)
  2179.     {
  2180.         AttVal* httpEquiv = AttrGetById(pNode, TidyAttr_HTTP_EQUIV);
  2181.         AttVal* metaContent = AttrGetById(pNode, TidyAttr_CONTENT);
  2182.  
  2183.         if ( !nodeIsMETA(pNode) || !metaContent ||
  2184.              !AttrMatches(httpEquiv, "Content-Type") )
  2185.             continue;
  2186.  
  2187.         pszBegin = s = tmbstrdup( metaContent->value );
  2188.         while (pszBegin && *pszBegin)
  2189.         {
  2190.             while (isspace( *pszBegin ))
  2191.                 pszBegin++;
  2192.             pszEnd = pszBegin;
  2193.             while ('\0' != *pszEnd && ';' != *pszEnd)
  2194.                 pszEnd++;
  2195.             if (';' == *pszEnd )
  2196.                 *(pszEnd++) = '\0';
  2197.             if (pszEnd > pszBegin)
  2198.             {
  2199.                 prop = (StyleProp *)MemAlloc(sizeof(StyleProp));
  2200.                 prop->name = tmbstrdup( pszBegin );
  2201.                 prop->value = NULL;
  2202.                 prop->next = NULL;
  2203.  
  2204.                 if (NULL != pLastProp)
  2205.                     pLastProp->next = prop;
  2206.                 else
  2207.                     pFirstProp = prop;
  2208.  
  2209.                 pLastProp = prop;
  2210.                 pszBegin = pszEnd;
  2211.             }
  2212.         }
  2213.         MemFree( s );
  2214.  
  2215.         /*  find the charset property */
  2216.         for (prop = pFirstProp; NULL != prop; prop = prop->next)
  2217.         {
  2218.             if (0 != tmbstrncasecmp( prop->name, "charset", 7 ))
  2219.                 continue;
  2220.  
  2221.             MemFree( prop->name );
  2222.             prop->name = (char*)MemAlloc( 32 );
  2223.             tmbsnprintf(prop->name, 32, "charset=%s", enc);
  2224.             s = CreatePropString( pFirstProp );
  2225.             MemFree( metaContent->value );
  2226.             metaContent->value = s;
  2227.             break;
  2228.         }
  2229.         /* #718127, prevent memory leakage */
  2230.         FreeStyleProps(pFirstProp);
  2231.         pLastProp = NULL;
  2232.     }
  2233. }
  2234.  
  2235. void DropComments(TidyDocImpl* doc, Node* node)
  2236. {
  2237.     Node* next;
  2238.  
  2239.     while (node)
  2240.     {
  2241.         next = node->next;
  2242.  
  2243.         if (node->type == CommentTag)
  2244.         {
  2245.             RemoveNode(node);
  2246.             FreeNode(doc, node);
  2247.             node = next;
  2248.             continue;
  2249.         }
  2250.  
  2251.         if (node->content)
  2252.             DropComments(doc, node->content);
  2253.  
  2254.         node = next;
  2255.     }
  2256. }
  2257.  
  2258. void DropFontElements(TidyDocImpl* doc, Node* node, Node **pnode)
  2259. {
  2260. #pragma unused(pnode)
  2261.  
  2262.     Node* next;
  2263.  
  2264.     while (node)
  2265.     {
  2266.         next = node->next;
  2267.  
  2268.         if (nodeIsFONT(node))
  2269.             DiscardContainer(doc, node, &next);
  2270.  
  2271.         if (node->content)
  2272.             DropFontElements(doc, node->content, &next);
  2273.  
  2274.         node = next;
  2275.     }
  2276. }
  2277.  
  2278. void WbrToSpace(TidyDocImpl* doc, Node* node)
  2279. {
  2280.     Node* next;
  2281.  
  2282.     while (node)
  2283.     {
  2284.         next = node->next;
  2285.  
  2286.         if (nodeIsWBR(node))
  2287.         {
  2288.             Node* text;
  2289.             text = NewLiteralTextNode(doc->lexer, " ");
  2290.             InsertNodeAfterElement(node, text);
  2291.             RemoveNode(node);
  2292.             FreeNode(doc, node);
  2293.             node = next;
  2294.             continue;
  2295.         }
  2296.  
  2297.         if (node->content)
  2298.             WbrToSpace(doc, node->content);
  2299.  
  2300.         node = next;
  2301.    }
  2302. }
  2303.  
  2304. /*
  2305.   Filters from Word and PowerPoint often use smart
  2306.   quotes resulting in character codes between 128
  2307.   and 159. Unfortunately, the corresponding HTML 4.0
  2308.   entities for these are not widely supported. The
  2309.   following converts dashes and quotation marks to
  2310.   the nearest ASCII equivalent. My thanks to
  2311.   Andrzej Novosiolov for his help with this code.
  2312.  
  2313.   Note: The old code in the pretty printer applied
  2314.   this to all node types and attribute values while
  2315.   this routine applies it only to text nodes. First,
  2316.   Microsoft Office products rarely put the relevant
  2317.   characters into these tokens, second support for
  2318.   them is much better now and last but not least, it
  2319.   can be harmful to replace these characters since
  2320.   US-ASCII quote marks are often used as syntax
  2321.   characters, a simple
  2322.  
  2323.     <a onmouseover="alert('‘')">...</a>
  2324.  
  2325.   would be broken if the U+2018 is replaced by "'".
  2326.   The old code would neither take care whether the
  2327.   quote mark is already used as delimiter,
  2328.  
  2329.     <p title='‘'>...</p>
  2330.  
  2331.   got
  2332.   
  2333.     <p title='''>...</p>
  2334.  
  2335.   Since browser support is much better nowadays and
  2336.   high-quality typography is better than ASCII it'd
  2337.   be probably a good idea to drop the feature...
  2338. */
  2339. void DowngradeTypography(TidyDocImpl* doc, Node* node)
  2340. {
  2341.     Node* next;
  2342.     Lexer* lexer = doc->lexer;
  2343.  
  2344.     while (node)
  2345.     {
  2346.         next = node->next;
  2347.  
  2348.         if (node->type == TextNode)
  2349.         {
  2350.             uint i, c;
  2351.             tmbstr p = lexer->lexbuf + node->start;
  2352.  
  2353.             for (i = node->start; i < node->end; ++i)
  2354.             {
  2355.                 c = (unsigned char) lexer->lexbuf[i];
  2356.  
  2357.                 if (c > 0x7F)
  2358.                     i += GetUTF8(lexer->lexbuf + i, &c);
  2359.  
  2360.                 if (c >= 0x2013 && c <= 0x201E)
  2361.                 {
  2362.                     switch (c)
  2363.                     {
  2364.                     case 0x2013: /* en dash */
  2365.                     case 0x2014: /* em dash */
  2366.                         c = '-';
  2367.                         break;
  2368.                     case 0x2018: /* left single  quotation mark */
  2369.                     case 0x2019: /* right single quotation mark */
  2370.                     case 0x201A: /* single low-9 quotation mark */
  2371.                         c = '\'';
  2372.                         break;
  2373.                     case 0x201C: /* left double  quotation mark */
  2374.                     case 0x201D: /* right double quotation mark */
  2375.                     case 0x201E: /* double low-9 quotation mark */
  2376.                         c = '"';
  2377.                         break;
  2378.                     }
  2379.                 }
  2380.  
  2381.                 p = PutUTF8(p, c);
  2382.             }
  2383.  
  2384.             node->end = p - lexer->lexbuf;
  2385.         }
  2386.  
  2387.         if (node->content)
  2388.             DowngradeTypography(doc, node->content);
  2389.  
  2390.         node = next;
  2391.     }
  2392. }
  2393.  
  2394. void ReplacePreformattedSpaces(TidyDocImpl* doc, Node* node)
  2395. {
  2396.     Node* next;
  2397.  
  2398.     while (node)
  2399.     {
  2400.         next = node->next;
  2401.  
  2402.         if (node->tag && node->tag->parser == ParsePre)
  2403.         {
  2404.             NormalizeSpaces(doc->lexer, node);
  2405.             node = next;
  2406.             continue;
  2407.         }
  2408.  
  2409.         if (node->content)
  2410.             ReplacePreformattedSpaces(doc, node->content);
  2411.  
  2412.         node = next;
  2413.     }
  2414. }
  2415.  
  2416. void ConvertCDATANodes(TidyDocImpl* doc, Node* node)
  2417. {
  2418.     Node* next;
  2419.  
  2420.     while (node)
  2421.     {
  2422.         next = node->next;
  2423.  
  2424.         if (node->type == CDATATag)
  2425.             node->type = TextNode;
  2426.  
  2427.         if (node->content)
  2428.             ConvertCDATANodes(doc, node->content);
  2429.  
  2430.         node = next;
  2431.     }
  2432. }
  2433.  
  2434. /*
  2435.   FixLanguageInformation ensures that the document contains (only)
  2436.   the attributes for language information desired by the output
  2437.   document type. For example, for XHTML 1.0 documents both
  2438.   'xml:lang' and 'lang' are desired, for XHTML 1.1 only 'xml:lang'
  2439.   is desired and for HTML 4.01 only 'lang' is desired.
  2440. */
  2441. void FixLanguageInformation(TidyDocImpl* doc, Node* node, Bool wantXmlLang, Bool wantLang)
  2442. {
  2443.     Node* next;
  2444.  
  2445.     while (node)
  2446.     {
  2447.         next = node->next;
  2448.  
  2449.         /* todo: report modifications made here to the report system */
  2450.  
  2451.         if (nodeIsElement(node))
  2452.         {
  2453.             AttVal* lang = AttrGetById(node, TidyAttr_LANG);
  2454.             AttVal* xmlLang = AttrGetById(node, TidyAttr_XML_LANG);
  2455.  
  2456.             if (lang && xmlLang)
  2457.             {
  2458.                 /*
  2459.                   todo: check whether both attributes are in sync,
  2460.                   here or elsewhere, where elsewhere is probably
  2461.                   preferable.
  2462.                 */
  2463.             }
  2464.             else if (lang && wantXmlLang)
  2465.             {
  2466.                 RepairAttrValue(doc, node, "xml:lang", lang->value);
  2467.             }
  2468.             else if (xmlLang && wantLang)
  2469.             {
  2470.                 RepairAttrValue(doc, node, "lang", xmlLang->value);
  2471.             }
  2472.  
  2473.             if (lang && !wantLang)
  2474.                 RemoveAttribute(doc, node, lang);
  2475.             
  2476.             if (xmlLang && !wantXmlLang)
  2477.                 RemoveAttribute(doc, node, xmlLang);
  2478.         }
  2479.  
  2480.         if (node->content)
  2481.             FixLanguageInformation(doc, node->content, wantXmlLang, wantLang);
  2482.  
  2483.         node = next;
  2484.     }
  2485. }
  2486.  
  2487. /*
  2488.   Set/fix/remove <html xmlns='...'>
  2489. */
  2490. void FixXhtmlNamespace(TidyDocImpl* doc, Bool wantXmlns)
  2491. {
  2492.     Node* html = FindHTML(doc);
  2493.     AttVal* xmlns;
  2494.  
  2495.     if (!html)
  2496.         return;
  2497.  
  2498.     xmlns = AttrGetById(html, TidyAttr_XMLNS);
  2499.  
  2500.     if (wantXmlns)
  2501.     {
  2502.         if (!AttrMatches(xmlns, XHTML_NAMESPACE))
  2503.             RepairAttrValue(doc, html, "xmlns", XHTML_NAMESPACE);
  2504.     }
  2505.     else if (xmlns)
  2506.     {
  2507.         RemoveAttribute(doc, html, xmlns);
  2508.     }
  2509. }
  2510.  
  2511. /*
  2512.   ...
  2513. */
  2514. void FixAnchors(TidyDocImpl* doc, Node *node, Bool wantName, Bool wantId, Bool xmlId)
  2515. {
  2516.     Node* next;
  2517.  
  2518.     while (node)
  2519.     {
  2520.         next = node->next;
  2521.  
  2522.         if (IsAnchorElement(doc, node))
  2523.         {
  2524.             AttVal *name = AttrGetById(node, TidyAttr_NAME);
  2525.             AttVal *id = AttrGetById(node, TidyAttr_ID);
  2526.  
  2527.             /* todo: how are empty name/id attributes handled? */
  2528.  
  2529.             if (name && id)
  2530.             {
  2531.                 /*
  2532.                   todo: check whether both attributes are in sync,
  2533.                   here or elsewhere, where elsewhere is probably
  2534.                   preferable.
  2535.                 */
  2536.             }
  2537.             else if (name && wantId)
  2538.             {
  2539.                 if (IsValidXMLID(name->value) || !xmlId)
  2540.                 {
  2541.                     RepairAttrValue(doc, node, "id", name->value);
  2542.                 }
  2543.                 else
  2544.                 {
  2545.                     ReportAttrError(doc, node, name, INVALID_XML_ID);
  2546.                 }
  2547.             }
  2548.             else if (id && wantName)
  2549.             {
  2550.                 /* todo: do not assume id is valid */
  2551.                 RepairAttrValue(doc, node, "name", id->value);
  2552.             }
  2553.  
  2554.             if (id && !wantId)
  2555.                 RemoveAttribute(doc, node, id);
  2556.             
  2557.             if (name && !wantName)
  2558.                 RemoveAttribute(doc, node, name);
  2559.  
  2560.             if (AttrGetById(node, TidyAttr_NAME) == NULL &&
  2561.                 AttrGetById(node, TidyAttr_ID) == NULL)
  2562.                 RemoveAnchorByNode(doc, node);
  2563.         }
  2564.  
  2565.         if (node->content)
  2566.             FixAnchors(doc, node->content, wantName, wantId, xmlId);
  2567.  
  2568.         node = next;
  2569.     }
  2570. }
  2571.